﻿using System;
using System.Collections.Generic;
using System.Drawing;

using GLSL;

using OpenTK;

namespace MosCraft.Graphics
{
    public class ChunkMeshBuilder
    {
        public RenderPass renderPass;
        private Chunk chunk;
        private World world;

        public ChunkMeshBuilder(World world)
        {
            this.world = world;
        }

        public void BuildMesh(Chunk chunk)
        {
            this.chunk = chunk;
            chunk.solidVertices = this.buildVertices(RenderPass.Solids);
            chunk.translucentVertices = this.buildVertices(RenderPass.Blended);
            chunk = null;
        }

        public Vertex[] buildVertices(RenderPass renderPass)
        {
            this.renderPass = renderPass;
            int xIndex = chunk.xPosition << 4;
            int zIndex = chunk.zPosition << 4;
            List<Vertex> Vertices = new List<Vertex>();

            for (int x = xIndex; x < (xIndex + Chunk._XDIM); x++)
            {
                for (int y = 0; y < Chunk._YDIM; y++)
                {
                    for (int z = zIndex; z < (zIndex + Chunk._ZDIM); z++)
                    {
                        // If this cell is air, then continue.  
                        if (world.isAirBlock(x, y, z))
                        {
                            continue;
                        }

                        Block block = world.getBlock(x, y, z);

                        if (block == null || block.getRenderBlockPass() != renderPass)
                        {
                            continue;
                        }

                        switch (block.getRenderType())
                        {
                            case RenderMode.STANDARD:
                                this.RenderRightFace(ref Vertices, block, x, y, z);
                                this.RenderLeftFace(ref Vertices, block, x, y, z);
                                this.RenderTopFace(ref Vertices, block, x, y, z);
                                this.RenderBottomFace(ref Vertices, block, x, y, z);
                                this.RenderFrontFace(ref Vertices, block, x, y, z);
                                this.RenderBackFace(ref Vertices, block, x, y, z);
                                break;

                            case RenderMode.CROSS:
                                this.renderCrossedSquares(ref Vertices, block, x, y, z);
                                break;
                        }
                    }
                }
            }
            return Vertices.ToArray();
        }

        private void RenderTopFace(ref List<Vertex> Vertices, Block block, int x, int y, int z)
        {
            Block oppsiteBlock = world.getBlock(x, y + 1, z);
            if (oppsiteBlock != null)
            {
                if (renderPass == RenderPass.Blended && oppsiteBlock.getRenderBlockPass() == RenderPass.Solids)
                {
                    return;
                }

                if (oppsiteBlock.getRenderBlockPass() == renderPass)
                {
                    if (!oppsiteBlock.RenderOpposites())
                    {
                        return;
                    }
                }
            }

            RectangleF texture = this.textureUV(block.getBlockTexture(BlockFace.Top));

            Vertex topRight = new Vertex();
            topRight.TextureUV = new Vector2(texture.Right, texture.Top);
            topRight.Postion = new Vector3((float)x + 1.0f, (float)y + 1.0f, (float)z);
            topRight.Normal = new Vector3(0f, 1.0f, 0f);
            Vertices.Add(topRight);

            Vertex topLeft = new Vertex();
            topLeft.TextureUV = new Vector2(texture.Left, texture.Top);
            topLeft.Postion = new Vector3((float)x, (float)y + 1.0f, (float)z);
            topLeft.Normal = new Vector3(0f, 1.0f, 0f);
            Vertices.Add(topLeft);

            Vertex bottomLeft = new Vertex();
            bottomLeft.TextureUV = new Vector2(texture.Left, texture.Bottom);
            bottomLeft.Postion = new Vector3((float)x, (float)y + 1.0f, (float)z + 1.0f);
            bottomLeft.Normal = new Vector3(0f, 1.0f, 0f);
            Vertices.Add(bottomLeft);

            Vertex bottomRight = new Vertex();
            bottomRight.TextureUV = new Vector2(texture.Right, texture.Bottom);
            bottomRight.Postion = new Vector3((float)x + 1.0f, (float)y + 1.0f, (float)z + 1.0f);
            bottomRight.Normal = new Vector3(0f, 1.0f, 0f);
            Vertices.Add(bottomRight);
        }

        private void RenderBottomFace(ref List<Vertex> Vertices, Block block, int x, int y, int z)
        {
            Block oppsiteBlock = world.getBlock(x, y - 1, z);
            if (oppsiteBlock != null)
            {
                if (renderPass == RenderPass.Blended && oppsiteBlock.getRenderBlockPass() == RenderPass.Solids)
                {
                    return;
                }

                if (oppsiteBlock.getRenderBlockPass() == renderPass)
                {
                    if (!oppsiteBlock.RenderOpposites())
                    {
                        return;
                    }
                }
            }

            RectangleF texture = this.textureUV(block.getBlockTexture(BlockFace.Bottom));

            Vertex topLeft = new Vertex();
            topLeft.TextureUV = new Vector2(texture.Right, texture.Top);
            topLeft.Postion = new Vector3((float)x, (float)y, (float)z);
            topLeft.Normal = new Vector3(0f, -1.0f, 0f);
            Vertices.Add(topLeft);

            Vertex topRight = new Vertex();
            topRight.TextureUV = new Vector2(texture.Left, texture.Top);
            topRight.Postion = new Vector3((float)x + 1.0f, (float)y, (float)z);
            topRight.Normal = new Vector3(0f, -1.0f, 0f);
            Vertices.Add(topRight);

            Vertex bottomRight = new Vertex();
            bottomRight.TextureUV = new Vector2(texture.Left, texture.Bottom);
            bottomRight.Postion = new Vector3((float)x + 1.0f, (float)y, (float)z + 1.0f);
            bottomRight.Normal = new Vector3(0f, -1.0f, 0f);
            Vertices.Add(bottomRight);

            Vertex bottomLeft = new Vertex();
            bottomLeft.TextureUV = new Vector2(texture.Right, texture.Bottom);
            bottomLeft.Postion = new Vector3((float)x, (float)y, (float)z + 1.0f);
            bottomLeft.Normal = new Vector3(0f, -1.0f, 0f);
            Vertices.Add(bottomLeft);
        }

        private void RenderLeftFace(ref List<Vertex> Vertices, Block block, int x, int y, int z)
        {
            Block oppsiteBlock = world.getBlock(x - 1, y, z);
            if (oppsiteBlock != null)
            {
                if (renderPass == RenderPass.Blended && oppsiteBlock.getRenderBlockPass() == RenderPass.Solids)
                {
                    return;
                }

                if (oppsiteBlock.getRenderBlockPass() == renderPass)
                {
                    if (!oppsiteBlock.RenderOpposites())
                    {
                        return;
                    }
                }
            }

            RectangleF texture = this.textureUV(block.getBlockTexture(BlockFace.Left));

            Vertex topRight = new Vertex();
            topRight.TextureUV = new Vector2(texture.Right, texture.Top);
            topRight.Postion = new Vector3((float)x, (float)y + 1.0f, (float)z + 1.0f);
            topRight.Normal = new Vector3(1.0f, 0f, 0f);
            Vertices.Add(topRight);

            Vertex topLeft = new Vertex();
            topLeft.TextureUV = new Vector2(texture.Left, texture.Top);
            topLeft.Postion = new Vector3((float)x, (float)y + 1.0f, (float)z);
            topLeft.Normal = new Vector3(1.0f, 0f, 0f);
            Vertices.Add(topLeft);

            Vertex bottomLeft = new Vertex();
            bottomLeft.TextureUV = new Vector2(texture.Left, texture.Bottom);
            bottomLeft.Postion = new Vector3((float)x, (float)y, (float)z);
            bottomLeft.Normal = new Vector3(1.0f, 0f, 0f);
            Vertices.Add(bottomLeft);

            Vertex bottomRight = new Vertex();
            bottomRight.TextureUV = new Vector2(texture.Right, texture.Bottom);
            bottomRight.Postion = new Vector3((float)x, (float)y, (float)z + 1.0f);
            bottomRight.Normal = new Vector3(1.0f, 0f, 0f);
            Vertices.Add(bottomRight);
        }

        private void RenderRightFace(ref List<Vertex> Vertices, Block block, int x, int y, int z)
        {
            Block oppsiteBlock = world.getBlock(x + 1, y, z);
            if (oppsiteBlock != null)
            {
                if (renderPass == RenderPass.Blended && oppsiteBlock.getRenderBlockPass() == RenderPass.Solids)
                {
                    return;
                }

                if (oppsiteBlock.getRenderBlockPass() == renderPass)
                {
                    if (!oppsiteBlock.RenderOpposites())
                    {
                        return;
                    }
                }
            }

            RectangleF texture = this.textureUV(block.getBlockTexture(BlockFace.Right));

            Vertex topRight = new Vertex();
            topRight.TextureUV = new Vector2(texture.Right, texture.Top);
            topRight.Postion = new Vector3((float)x + 1.0f, (float)y + 1.0f, (float)z);
            topRight.Normal = new Vector3(1.0f, 0f, 0f);
            Vertices.Add(topRight);

            Vertex topLeft = new Vertex();
            topLeft.TextureUV = new Vector2(texture.Left, texture.Top);
            topLeft.Postion = new Vector3((float)x + 1.0f, (float)y + 1.0f, (float)z + 1.0f);
            topLeft.Normal = new Vector3(1.0f, 0f, 0f);
            Vertices.Add(topLeft);

            Vertex bottomLeft = new Vertex();
            bottomLeft.TextureUV = new Vector2(texture.Left, texture.Bottom);
            bottomLeft.Postion = new Vector3((float)x + 1.0f, (float)y, (float)z + 1.0f);
            bottomLeft.Normal = new Vector3(1.0f, 0f, 0f);
            Vertices.Add(bottomLeft);

            Vertex bottomRight = new Vertex();
            bottomRight.TextureUV = new Vector2(texture.Right, texture.Bottom);
            bottomRight.Postion = new Vector3((float)x + 1.0f, (float)y, (float)z);
            bottomRight.Normal = new Vector3(1.0f, 0f, 0f);
            Vertices.Add(bottomRight);
        }

        private void RenderFrontFace(ref List<Vertex> Vertices, Block block, int x, int y, int z)
        {
            Block oppsiteBlock = world.getBlock(x, y, z + 1);
            if (oppsiteBlock != null)
            {
                if (renderPass == RenderPass.Blended && oppsiteBlock.getRenderBlockPass() == RenderPass.Solids)
                {
                    return;
                }

                if (oppsiteBlock.getRenderBlockPass() == renderPass)
                {
                    if (!oppsiteBlock.RenderOpposites())
                    {
                        return;
                    }
                }
            }

            RectangleF texture = this.textureUV(block.getBlockTexture(BlockFace.Front));

            Vertex topRight = new Vertex();
            topRight.TextureUV = new Vector2(texture.Right, texture.Top);
            topRight.Postion = new Vector3((float)x + 1.0f, (float)y + 1.0f, (float)z + 1.0f);
            topRight.Normal = new Vector3(0f, 0f, 1.0f);
            Vertices.Add(topRight);

            Vertex topLeft = new Vertex();
            topLeft.TextureUV = new Vector2(texture.Left, texture.Top);
            topLeft.Postion = new Vector3((float)x, (float)y + 1.0f, (float)z + 1.0f);
            topLeft.Normal = new Vector3(0f, 0f, 1.0f);
            Vertices.Add(topLeft);

            Vertex bottomLeft = new Vertex();
            bottomLeft.TextureUV = new Vector2(texture.Left, texture.Bottom);
            bottomLeft.Postion = new Vector3((float)x, (float)y, (float)z + 1.0f);
            bottomLeft.Normal = new Vector3(0f, 0f, 1.0f);
            Vertices.Add(bottomLeft);

            Vertex bottomRight = new Vertex();
            bottomRight.TextureUV = new Vector2(texture.Right, texture.Bottom);
            bottomRight.Postion = new Vector3((float)x + 1.0f, (float)y, (float)z + 1.0f);
            bottomRight.Normal = new Vector3(0f, 0f, 1.0f);
            Vertices.Add(bottomRight);
        }

        private void RenderBackFace(ref List<Vertex> Vertices, Block block, int x, int y, int z)
        {
            Block oppsiteBlock = world.getBlock(x, y, z - 1);
            if (oppsiteBlock != null)
            {
                if (renderPass == RenderPass.Blended && oppsiteBlock.getRenderBlockPass() == RenderPass.Solids)
                {
                    return;
                }

                if (oppsiteBlock.getRenderBlockPass() == renderPass)
                {
                    if (!oppsiteBlock.RenderOpposites())
                    {
                        return;
                    }
                }
            }

            RectangleF texture = this.textureUV(block.getBlockTexture(BlockFace.Back));

            Vertex topRight = new Vertex();
            topRight.TextureUV = new Vector2(texture.Right, texture.Top);
            topRight.Postion = new Vector3((float)x, (float)y + 1.0f, (float)z);
            topRight.Normal = new Vector3(0f, 0f, 1.0f);
            Vertices.Add(topRight);

            Vertex topLeft = new Vertex();
            topLeft.TextureUV = new Vector2(texture.Left, texture.Top);
            topLeft.Postion = new Vector3((float)x + 1.0f, (float)y + 1.0f, (float)z);
            topLeft.Normal = new Vector3(0f, 0f, 1.0f);
            Vertices.Add(topLeft);

            Vertex bottomLeft = new Vertex();
            bottomLeft.TextureUV = new Vector2(texture.Left, texture.Bottom);
            bottomLeft.Postion = new Vector3((float)x + 1.0f, (float)y, (float)z);
            bottomLeft.Normal = new Vector3(0f, 0f, 1.0f);
            Vertices.Add(bottomLeft);

            Vertex bottomRight = new Vertex();
            bottomRight.TextureUV = new Vector2(texture.Right, texture.Bottom);
            bottomRight.Postion = new Vector3((float)x, (float)y, (float)z);
            bottomRight.Normal = new Vector3(0f, 0f, 1.0f);
            Vertices.Add(bottomRight);
        }

        private void renderCrossedSquares(ref List<Vertex> Vertices, Block block, int x, int y, int z)
        {
            RectangleF texture = this.textureUV(block.getBlockTexture(BlockFace.None));

            Vertex vertex = new Vertex();

            vertex.TextureUV = new Vector2(texture.Right, texture.Top);
            vertex.Postion = new Vector3((float)x, (float)y + 1.0f, (float)z);
            vertex.Normal = new Vector3(0f, 0f, 1.0f);
            Vertices.Add(vertex);
            vertex = new Vertex();
            vertex.TextureUV = new Vector2(texture.Left, texture.Top);
            vertex.Postion = new Vector3((float)x + 1.0f, (float)y + 1.0f, (float)z + 1.0f);
            vertex.Normal = new Vector3(0f, 0f, 1.0f);
            Vertices.Add(vertex);
            vertex = new Vertex();
            vertex.TextureUV = new Vector2(texture.Left, texture.Bottom);
            vertex.Postion = new Vector3((float)x + 1.0f, (float)y, (float)z + 1.0f);
            vertex.Normal = new Vector3(0f, 0f, 1.0f);
            Vertices.Add(vertex);
            vertex = new Vertex();
            vertex.TextureUV = new Vector2(texture.Right, texture.Bottom);
            vertex.Postion = new Vector3((float)x, (float)y, (float)z);
            vertex.Normal = new Vector3(0f, 0f, 1.0f);
            Vertices.Add(vertex);

            vertex.TextureUV = new Vector2(texture.Left, texture.Top);
            vertex.Postion = new Vector3((float)x + 1.0f, (float)y + 1.0f, (float)z + 1.0f);
            vertex.Normal = new Vector3(0f, 0f, 1.0f);
            Vertices.Add(vertex);
            vertex = new Vertex();
            vertex.TextureUV = new Vector2(texture.Right, texture.Top);
            vertex.Postion = new Vector3((float)x, (float)y + 1.0f, (float)z);
            vertex.Normal = new Vector3(0f, 0f, 1.0f);
            Vertices.Add(vertex);
            vertex = new Vertex();
            vertex.TextureUV = new Vector2(texture.Right, texture.Bottom);
            vertex.Postion = new Vector3((float)x, (float)y, (float)z);
            vertex.Normal = new Vector3(0f, 0f, 1.0f);
            Vertices.Add(vertex);
            vertex = new Vertex();
            vertex.TextureUV = new Vector2(texture.Left, texture.Bottom);
            vertex.Postion = new Vector3((float)x + 1.0f, (float)y, (float)z + 1.0f);
            vertex.Normal = new Vector3(0f, 0f, 1.0f);
            Vertices.Add(vertex);

            vertex.TextureUV = new Vector2(texture.Right, texture.Top);
            vertex.Postion = new Vector3((float)x + 1.0f, (float)y + 1.0f, (float)z);
            vertex.Normal = new Vector3(0f, 0f, 1.0f);
            Vertices.Add(vertex);
            vertex = new Vertex();
            vertex.TextureUV = new Vector2(texture.Left, texture.Top);
            vertex.Postion = new Vector3((float)x, (float)y + 1.0f, (float)z + 1.0f);
            vertex.Normal = new Vector3(0f, 0f, 1.0f);
            Vertices.Add(vertex);
            vertex = new Vertex();
            vertex.TextureUV = new Vector2(texture.Left, texture.Bottom);
            vertex.Postion = new Vector3((float)x, (float)y, (float)z + 1.0f);
            vertex.Normal = new Vector3(0f, 0f, 1.0f);
            Vertices.Add(vertex);
            vertex = new Vertex();
            vertex.TextureUV = new Vector2(texture.Right, texture.Bottom);
            vertex.Postion = new Vector3((float)x + 1.0f, (float)y, (float)z);
            vertex.Normal = new Vector3(0f, 0f, 1.0f);
            Vertices.Add(vertex);

            vertex.TextureUV = new Vector2(texture.Left, texture.Top);
            vertex.Postion = new Vector3((float)x, (float)y + 1.0f, (float)z + 1.0f);
            vertex.Normal = new Vector3(0f, 0f, 1.0f);
            Vertices.Add(vertex);
            vertex = new Vertex();
            vertex.TextureUV = new Vector2(texture.Right, texture.Top);
            vertex.Postion = new Vector3((float)x + 1.0f, (float)y + 1.0f, (float)z);
            vertex.Normal = new Vector3(0f, 0f, 1.0f);
            Vertices.Add(vertex);
            vertex = new Vertex();
            vertex.TextureUV = new Vector2(texture.Right, texture.Bottom);
            vertex.Postion = new Vector3((float)x + 1.0f, (float)y, (float)z);
            vertex.Normal = new Vector3(0f, 0f, 1.0f);
            Vertices.Add(vertex);
            vertex = new Vertex();
            vertex.TextureUV = new Vector2(texture.Left, texture.Bottom);
            vertex.Postion = new Vector3((float)x, (float)y, (float)z + 1.0f);
            vertex.Normal = new Vector3(0f, 0f, 1.0f);
            Vertices.Add(vertex);
        }

        private RectangleF textureUV(int textureID)
        {
            int Colums = 16;
            int Rows = 16;
            return new RectangleF((textureID % Colums) / (float)Colums,
                (textureID / Colums) / (float)Rows,
                1.0f / Colums,
                1.0f / Rows);
        }
    }
}
